﻿using Microsoft.Practices.Prism.Mvvm;
using Microsoft.Practices.Prism.Mvvm.Interfaces;
using Microsoft.Practices.Prism.StoreApps;
using Microsoft.Practices.Prism.StoreApps.Interfaces;
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace PrismAdapter
{
    public class PrismAdapterBootstrapper
    {

        private static readonly ThreadLocal<PrismAdapterBootstrapper> current = new ThreadLocal<PrismAdapterBootstrapper>();

        /// <summary>
        /// default bootstrap sequence.
        /// </summary>
        /// <param name="initProcess">navigation process</param>
        /// <param name="args">launched or activated event args</param>
        /// <param name="knownTypes">terminate saved types.</param>
        /// <returns></returns>
        public static async Task Initialize(
            Func<PrismAdapterBootstrapper, Task> initProcess,
            IActivatedEventArgs args,
            Type[] knownTypes = null)
        {
            if (PrismAdapterBootstrapper.IsInitialized)
            {
                Window.Current.Activate();
                return;
            }

            var b = new PrismAdapterBootstrapper();
            await b.Setup(args, knownTypes);
            await initProcess(b);
        }


        /// <summary>
        /// get Current instance.
        /// </summary>
        public static PrismAdapterBootstrapper Current
        {
            get { return current.Value; }
        }

        /// <summary>
        /// get Initialized PrismAdapter
        /// </summary>
        public static bool IsInitialized
        {
            get { return current.IsValueCreated; }
        }

        /// <summary>
        /// get application root frame instance.
        /// </summary>
        public Frame RootFrame { get; private set; }

        /// <summary>
        /// ISessionStateService factory.
        /// </summary>
        public Func<ISessionStateService> SessionStateServiceFactory { get; set; }

        /// <summary>
        /// IFrameFacade factory.
        /// </summary>
        public Func<Frame, IFrameFacade> FrameFacadeFactory { get; set; }

        /// <summary>
        /// INavigationService factory.
        /// </summary>
        public Func<INavigationService> NavigationServiceFactory { get; set; }

        /// <summary>
        /// SplashScreen factory.
        /// </summary>
        public Func<SplashScreen, Page> ExtendedSplashScreenFactory { get; set; }

        /// <summary>
        /// Resolve object instance delegate.
        /// </summary>
        public Func<Type, object> Resolve { get; set; }

        /// <summary>
        /// token -> PageType delegate.
        /// </summary>
        public Func<string, Type> GetPageType { get; set; }

        /// <summary>
        /// SessionStateService instance.
        /// </summary>
        public Lazy<ISessionStateService> SessionStateService { get; set; }

        /// <summary>
        /// get FrameFacade instance.
        /// </summary>
        public Lazy<IFrameFacade> FrameFacade { get; set; }

        /// <summary>
        /// get NavigationService instance.
        /// </summary>
        public Lazy<INavigationService> NavigationService { get; set; }

        /// <summary>
        /// OnLaunched EventArgs.
        /// </summary>
        internal IActivatedEventArgs ActivatedEventArgs { get; set; }

        /// <summary>
        /// Activated by ShareTarget.
        /// </summary>
        private bool IsShareTargetActivated
        {
            get { return this.ActivatedEventArgs is ShareTargetActivatedEventArgs; }
        }

        /// <summary>
        /// get susupending status.
        /// </summary>
        public bool IsSuspending { get; private set; }

        /// <summary>
        /// Constructor method.
        /// </summary>
        public PrismAdapterBootstrapper()
        {
            if (current.IsValueCreated)
            {
                throw new InvalidOperationException("already created.");
            }

            current.Value = this;
            Application.Current.Suspending += this.OnSuspending;
        }

        private async void OnSuspending(object sender, SuspendingEventArgs e)
        {
            this.IsSuspending = true;
            try
            {
                var def = e.SuspendingOperation.GetDeferral();

                this.NavigationService.Value.Suspending();
                await this.SessionStateService.Value.SaveAsync();

                def.Complete();
            }
            finally
            {
                IsSuspending = false;
            }
        }

        /// <summary>
        /// Setup Prism.StoreApp objects.
        /// </summary>
        /// <param name="args">OnLaunched EventArgs</param>
        /// <returns></returns>
        public async Task Setup(IActivatedEventArgs args, Type[] knownTypes = null)
        {
            if (args == null)
            {
                throw new ArgumentNullException("args");
            }

            this.ActivatedEventArgs = args;

            if (args.PreviousExecutionState == ApplicationExecutionState.Running && !this.IsShareTargetActivated)
            {
                return;
            }

            this.RootFrame = Window.Current.Content as Frame;
            if (this.RootFrame == null)
            {
                this.RootFrame = new Frame();
                this.InitializeFactories(this.RootFrame);

                VisualStateAwarePage.GetSessionStateForFrame = frame => 
                    SessionStateService.Value.GetSessionStateForFrame(this.FrameFacade.Value);

                this.SessionStateService.Value.RegisterFrame(this.FrameFacade.Value, "AppFrame");

                if (knownTypes != null && knownTypes.Any())
                {
                    foreach (var type in knownTypes)
                    {
                        this.SessionStateService.Value.RegisterKnownType(type);
                    }
                }

                if (this.ActivatedEventArgs.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    await SessionStateService.Value.RestoreSessionStateAsync();
                }
            }
        }

        /// <summary>
        /// Run application
        /// </summary>
        /// <param name="navigatePageAction">navigate to first page process.</param>
        public void Run(Action<INavigationService> navigatePageAction)
        {
            if (this.ActivatedEventArgs.PreviousExecutionState != ApplicationExecutionState.Running || this.IsShareTargetActivated)
            {
                if (this.ExtendedSplashScreenFactory != null)
                {
                    var splashScreen = this.ExtendedSplashScreenFactory(this.ActivatedEventArgs.SplashScreen);
                    this.RootFrame.Content = splashScreen;
                }

                ViewModelLocationProvider.SetDefaultViewModelFactory(this.Resolve);
                bool restoringFromTermination = false;
                if (this.ActivatedEventArgs.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    try
                    {
                        this.SessionStateService.Value.RestoreFrameState();
                        this.NavigationService.Value.RestoreSavedNavigation();
                        restoringFromTermination = true;
                    }
                    catch (SessionStateServiceException)
                    {
                        // nop
                    }
                }

                if (Window.Current.Content == null)
                {
                    Window.Current.Content = this.RootFrame;
                }

                if (this.RootFrame != null && !restoringFromTermination)
                {
                    navigatePageAction(this.NavigationService.Value);
                }
            }

            Window.Current.Activate();
        }

        private void InitializeFactories(Frame rootFrame)
        {
            if (this.Resolve == null)
            {
                this.Resolve = DefaultResolve;
            }

            if (this.GetPageType == null)
            {
                this.GetPageType = DefaultGetPageType;
            }

            if (this.FrameFacadeFactory == null)
            {
                this.FrameFacadeFactory = f => new FrameFacadeAdapter(rootFrame);
            }

            this.FrameFacade = new Lazy<IFrameFacade>(() => this.FrameFacadeFactory(rootFrame));
            if (this.SessionStateServiceFactory == null)
            {
                this.SessionStateServiceFactory = () => new SessionStateService();
            }
            this.SessionStateService = new Lazy<ISessionStateService>(this.SessionStateServiceFactory);

            if (this.NavigationServiceFactory == null)
            {
                this.NavigationServiceFactory = () =>
                    new FrameNavigationService(this.FrameFacade.Value, this.GetPageType, this.SessionStateService.Value);
            }
            this.NavigationService = new Lazy<INavigationService>(this.NavigationServiceFactory);

        }

        private static Type DefaultGetPageType(string pageToken)
        {
            var assemblyQualifiedAppType = Application.Current.GetType().GetTypeInfo().AssemblyQualifiedName;

            var pageNameWithParameter = assemblyQualifiedAppType.Replace(
                Application.Current.GetType().FullName,
                Application.Current.GetType().Namespace + ".Views.{0}Page");

            var viewFullName = string.Format(CultureInfo.InvariantCulture, pageNameWithParameter, pageToken);
            var viewType = Type.GetType(viewFullName);

            if (viewType == null)
            {
                pageNameWithParameter = assemblyQualifiedAppType.Replace(
                    Application.Current.GetType().FullName,
                    Application.Current.GetType().Namespace + ".Views.{0}Flyout");

                viewFullName = string.Format(CultureInfo.InvariantCulture, pageNameWithParameter, pageToken);
                viewType = Type.GetType(viewFullName);
            }

            if (viewType == null)
            {
                throw new ArgumentException("pageToken " + pageToken);
            }

            return viewType;
        }

        private static object DefaultResolve(Type type)
        {
            return Activator.CreateInstance(type);
        }

    }
}
